﻿/* ===============================================================================  
 * Copyright (c) 2010 Acid Frameworks
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * =============================================================================== */

using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;


namespace Acid.Network.Tcp
{
    public delegate void OnConnect     (Client sender);
    public delegate void OnError       (Client sender, string message);
    public delegate void OnPacket      (Client sender, Packet packet);

    /// <summary>
    /// Tcp Broadcaster.
    /// </summary>
    public class Client : IDisposable
    {
        private      TcpClient              client;
        private      BinaryReader           reader;
        private      BinaryWriter           writer;
        private      Thread                 listenThread;
        private      bool                   running;
        private      int                    bufferSize;
        

        public event OnPacket               OnPacket;
        public event OnConnect              OnConnected;
        public event OnError                OnError;

        public Client() : this(1024) { }

        public Client(int bufferSize)
        {
            this.client     = new TcpClient();

            this.bufferSize = bufferSize;
        }

        public Client(TcpClient client, int bufferSize)
        {
            this.bufferSize   = bufferSize;

            this.client       = client;

            this.reader       = new BinaryReader(this.client.GetStream());

            this.writer       = new BinaryWriter(this.client.GetStream());

            this.running      = true;
            
            this.listenThread = new Thread(new ThreadStart(this.ListenThread));

            this.listenThread.Start();
        }

        #region Properties

        public bool Connected
        {
            get
            {
                return this.client.Connected; 
            }
        }

        #endregion

        #region Methods

        public void Send(Packet packet)
        {
            try
            {
                if (this.client.Connected)
                {
                    byte [] sendData = new byte[this.bufferSize];

                    byte[] data = Packet.Serialize(packet);

                    Array.Copy(data, sendData, data.Length);

                    this.writer.Write(sendData, 0, this.bufferSize);

                    this.writer.Flush();
                }
            }
            catch (Exception e)
            {
                if (this.OnError != null)
                {
                    this.OnError(this, e.Message);
                }
            }
        }

        /// <summary>
        /// Will attempt to connect to a remote host. The remote host needs to
        /// accept before any packets can be sent.
        /// </summary>
        public void Connect(string hostname, int port)
        {
            try
            {
                this.client.Connect(hostname, port);

                this.reader       = new BinaryReader(this.client.GetStream());

                this.writer       = new BinaryWriter(this.client.GetStream());

                this.running      = true;

                this.listenThread = new Thread(new ThreadStart(this.ListenThread));

                this.listenThread.Start();

                if (this.OnConnected != null)
                {
                    this.OnConnected(this);
                }
            }
            catch(Exception e)
            {
                if (this.OnError != null)
                {
                    this.OnError(this, e.Message);
                }
            }
        }

        /// <summary>
        /// Disconnects from the host. Similar to Dispose() except a attempt is made to notity the server of the disconnect.
        /// </summary>
        public void Disconnect()
        {
            this.Send(new Packet("_close_"));

            this.Dispose(true);
        }

        #endregion

        #region Threads

        private void ListenThread()
        {
            while (this.running)
            {
                try
                {
                    byte [] data = reader.ReadBytes(15);

                    Packet packet = Packet.Deserialize(data);

                    if (this.OnPacket != null)
                    {
                        this.OnPacket(this, packet);
                    }
                }
                catch (Exception e)
                {
                    if (this.OnError != null)
                    {
                        this.OnError(this, e.Message);
                    }
                }
            }
        }

        #endregion

        #region IDisposable Members

        private bool disposed = false;

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                

                if (!disposed)
                {
                    this.running = false;

                    this.listenThread.Abort();

                    this.client.Close();

                    this.reader.Close();
                    
                    this.writer.Close();
                }

                this.disposed = true;
            }
        }
        public void Dispose()
        {
            this.Dispose(true);
        }
        ~Client()
        {
            this.Dispose(false);
        }

        #endregion
    }
}
